package com.societyGames.flashForms
{
import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.geom.Rectangle;

public class Window extends SingleItemContainer
{
  private var _desiredWidth:Number;
  private var _desiredHeight:Number;
  private var _body:Sprite;
  private var _borderTop:Number;
  private var _borderLeft:Number;
  private var _borderBottom:Number;
  private var _borderRight:Number;
  private var _borderMoveTop:Number;
  private var _dragBounds:Rectangle;
  private var _dragType:DragType;
  private var _isLocked:Boolean; //Tracks whether mouse cursor is locked.
  private var _lastMouseX:Number;
  private var _lastMouseY:Number;
  private var _allowResize:Boolean = true;

  private var _header:DisplayObject;

  private var _leftRightCursor:DisplayObject;
  private var _upDownCursor:DisplayObject;
  private var _leftDownCursor:DisplayObject;
  private var _rightDownCursor:DisplayObject;

  public function Window(body:Sprite, borderLeft:Number, borderRight:Number, borderTop:Number, borderBottom:Number, borderMoveTop:Number, leftRightCursor:DisplayObject, upDownCursor:DisplayObject, leftDownCursor:DisplayObject, rightDownCursor:DisplayObject)
  {
    this._body = body;
    this._borderLeft = borderLeft;
    this._borderRight = borderRight;
    this._borderTop = borderTop;
    this._borderBottom = borderBottom;
    this._borderMoveTop = borderMoveTop;
    _leftRightCursor = leftRightCursor;
    _upDownCursor = upDownCursor;
    _leftDownCursor = leftDownCursor;
    _rightDownCursor = rightDownCursor;

    this._body.y = 0;
    this.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
    this.addEventListener(MouseEvent.ROLL_OUT, rollOutHandler);
    this.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
    this.addChild(this._body);

    this._desiredWidth = width;
    this._desiredHeight = height;
  }

  public override function set width(value:Number):void
  {
    if (value != this._desiredWidth)
    {
      this._desiredWidth = value;
      this._body.width = this._desiredWidth;
      recalculateChildWidth();
      recalculateHeaderWidth();
    }
  }

  public override function set height(value:Number):void
  {
    if (value != this._desiredHeight)
    {
      this._desiredHeight = value;
      this._body.height = this._desiredHeight;
      recalculateChildHeight();
      recalculateHeaderHeight();
    }
  }

  public override function set item(value:DisplayObject):void
  {
    super.item = value;
    this._item.x = this._borderLeft;
    this._item.y = this._borderTop + this._borderMoveTop;
    recalculateChildWidth();
    recalculateChildHeight();
  }

  //Whether you can drag the window larger or smaller.
  public function get allowResize():Boolean
  {
    return this._allowResize;
  }

  //Whether you can drag the window larger or smaller.
  public function set allowResize(value:Boolean):void
  {
    this._allowResize = value;
  }

  //Set space for the child.
  public function set innerWidth(value:Number):void
  {
    width = value + _borderLeft + _borderRight;
  }

  //Get space for the child.
  public function get innerWidth():Number
  {
    return _desiredWidth - (_borderLeft + _borderRight);
  }

  //Set space for the child.
  public function set innerHeight(value:Number):void
  {
    height = value + _borderTop + _borderMoveTop + _borderBottom;
  }

  //Get space for the child.
  public function get innerHeight():Number
  {
    return _desiredHeight - (_borderTop + _borderMoveTop + _borderBottom);
  }

  public function set header(value:DisplayObject):void
  {
    this._header = value;
    this._header.x = this._borderLeft;
    this._header.y = this._borderTop;
    this.addChild(this._header);
    recalculateHeaderWidth();
    recalculateHeaderHeight();
  }

  public function set dragBounds(value:Rectangle):void
  {
    this._dragBounds = value;
  }

  private function recalculateChildWidth():void
  {
    if (this._item != null)
    {
      this._item.width = _borderLeft + _borderRight > this._desiredWidth ? 0 : this._desiredWidth - (_borderLeft + _borderRight);
    }
  }

  private function recalculateChildHeight():void
  {
    if (this._item != null)
    {
      this._item.height = _borderTop + _borderMoveTop + _borderBottom > this._desiredHeight ? 0 : this._desiredHeight - (_borderTop + _borderMoveTop + _borderBottom);
    }
  }

  private function recalculateHeaderWidth():void
  {
    if (this._header != null)
    {
      this._header.width = _borderLeft + _borderRight > this._desiredWidth ? Math.max(0, this._desiredWidth - _borderLeft) : this._desiredWidth - (_borderLeft + _borderRight);
    }
  }

  private function recalculateHeaderHeight():void
  {
    if (this._header != null)
    {
      this._header.height = _borderTop + _borderMoveTop > this._desiredHeight ? Math.max(0, this._desiredHeight - _borderTop) : _borderMoveTop;
    }
  }

  private function mouseMoveHandler(event:MouseEvent = null):void
  {
    var newDragType:DragType = null;

    if (this._allowResize)
    {
      //In the left border
      if (this.mouseX < this._borderLeft && this.mouseX > 0)
      {
        if (this.mouseY < this._borderTop)
        {
          newDragType = DragType.LeftTop;
        }
        else if (this.mouseY > this._desiredHeight - this._borderBottom)
        {
          newDragType = DragType.LeftBottom;
        }
        else
        {
          newDragType = DragType.Left;
        }
      }
      //In the right border
      else if (this.mouseX > this._desiredWidth - this._borderRight && this.mouseX < this._desiredWidth)
      {
        if (this.mouseY < this._borderTop)
        {
          newDragType = DragType.RightTop;
        }
        else if (this.mouseY > this._desiredHeight - this._borderBottom)
        {
          newDragType = DragType.RightBottom;
        }
        else
        {
          newDragType = DragType.Right;
        }
      }
      //In the top
      else if (this.mouseY < this._borderTop)
      {
        newDragType = DragType.Top;
      }
      //In the bottom
      else if (this.mouseY > this._desiredHeight - this._borderBottom && this.mouseY < this._desiredHeight)
      {
        newDragType = DragType.Bottom;
      }
    } //End if allow resize.
    //In the move area
    if (this.mouseY < this._borderTop + this._borderMoveTop && this.mouseY > this._borderTop)
    {
      newDragType = DragType.Move;
    }
    showDragType(newDragType, false);
  }

  private function rollOutHandler(event:MouseEvent):void
  {
    showDragType(null, false);
  }

  private function showDragType(dragType:DragType, shouldLock:Boolean):void
  {
    if (this._dragType != dragType || this._isLocked != shouldLock)
    {
      this._dragType = dragType;
      this._isLocked = shouldLock;
      if (this._isLocked)
      {
        switch (this._dragType)
        {
          case null:
          case DragType.Move:
            MouseCursor.instance.unlockCursor(this);
            break;
          case DragType.Left:
          case DragType.Right:
            MouseCursor.instance.lockCursor(_leftRightCursor, this, -_leftRightCursor.width / 2, -_leftRightCursor.height / 2);
            break;
          case DragType.Top:
          case DragType.Bottom:
            MouseCursor.instance.lockCursor(_upDownCursor, this, -_upDownCursor.width / 2, -_upDownCursor.height / 2);
            break;
          case DragType.RightTop:
          case DragType.LeftBottom:
            MouseCursor.instance.lockCursor(_leftDownCursor, this, -_upDownCursor.width / 2, -_upDownCursor.height / 2);
            break;
          case DragType.LeftTop:
          case DragType.RightBottom:
            MouseCursor.instance.lockCursor(_rightDownCursor, this, -_upDownCursor.width / 2, -_upDownCursor.height / 2);
            break;
        }
      }
      else
      {
        switch (this._dragType)
        {
          case null:
          case DragType.Move:
            MouseCursor.instance.unsetCursor(this);
            break;
          case DragType.Left:
          case DragType.Right:
            MouseCursor.instance.setCursor(_leftRightCursor, this, -_leftRightCursor.width / 2, -_leftRightCursor.height / 2);
            break;
          case DragType.Top:
          case DragType.Bottom:
            MouseCursor.instance.setCursor(_upDownCursor, this, -_upDownCursor.width / 2, -_upDownCursor.height / 2);
            break;
          case DragType.RightTop:
          case DragType.LeftBottom:
            MouseCursor.instance.setCursor(_leftDownCursor, this, -_upDownCursor.width / 2, -_upDownCursor.height / 2);
            break;
          case DragType.LeftTop:
          case DragType.RightBottom:
            MouseCursor.instance.setCursor(_rightDownCursor, this, -_rightDownCursor.width / 2, -_rightDownCursor.height / 2);
            break;
        }
      }
    }
  }

  private function lockDragType():void
  {
    showDragType(this._dragType, true);
  }

  private function unlockDragType():void
  {
    MouseCursor.instance.unlockCursor(this);
  }

  private function mouseDownHandler(event:MouseEvent):void
  {
    this.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
    this.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
    this.removeEventListener(MouseEvent.ROLL_OUT, rollOutHandler);
    this.stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
    if (this._dragType != null)
    {
      this._lastMouseX = this.mouseX;
      this._lastMouseY = this.mouseY;
    }
    switch (this._dragType)
    {
      case DragType.Move:
        this.startDrag();
        break;
      case DragType.LeftTop:
        this.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveLeftTopHandler);
        break;
      case DragType.LeftBottom:
        this.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveLeftBottomHandler);
        break;
      case DragType.RightTop:
        this.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveRightTopHandler);
        break;
      case DragType.RightBottom:
        this.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveRightBottomHandler);
        break;
      case DragType.Left:
        this.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveLeftHandler);
        break;
      case DragType.Right:
        this.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveRightHandler);
        break;
      case DragType.Bottom:
        this.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveBottomHandler);
        break;
      case DragType.Top:
        this.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveTopHandler);
        break;
    }
    lockDragType();
  }

  private function mouseUpHandler(event:MouseEvent):void
  {
    this.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
    this.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
    this.addEventListener(MouseEvent.ROLL_OUT, rollOutHandler);

    this.stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
    switch (this._dragType)
    {
      case DragType.Move:
        this.stopDrag();
        break;
      case DragType.LeftTop:
        this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveLeftTopHandler);
        break;
      case DragType.LeftBottom:
        this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveLeftBottomHandler);
        break;
      case DragType.RightTop:
        this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveRightTopHandler);
        break;
      case DragType.RightBottom:
        this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveRightBottomHandler);
        break;
      case DragType.Left:
        this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveLeftHandler);
        break;
      case DragType.Right:
        this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveRightHandler);
        break;
      case DragType.Bottom:
        this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveBottomHandler);
        break;
      case DragType.Top:
        this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveTopHandler);
        break;
    }
    unlockDragType();
    if (this.hitTestPoint(event.stageX, event.stageY, true))
    {
      mouseMoveHandler(); //Refresh the cursor in case we got dragged over something valid.
    }
  }

  private function clampWidthDelta(dx:Number):Number
  {
    return this._desiredWidth + dx < (this._borderLeft + this._borderRight) ? this._desiredWidth - (this._borderLeft + this._borderRight) : dx;
  }

  private function clampHeightDelta(dy:Number):Number
  {
    return (this._desiredHeight + dy) < (this._borderTop + this._borderMoveTop + this._borderBottom)
            ? this._desiredHeight - (this._borderTop + this._borderMoveTop + this._borderBottom)
            : dy;
  }

  private function mouseMoveLeftTopHandler(event:MouseEvent):void
  {
    var dx:Number = clampWidthDelta(this._lastMouseX - this.mouseX);
    this.width = this._desiredWidth + dx;
    this.x = int(this.x - dx);

    var dy:Number = clampHeightDelta(this._lastMouseY - this.mouseY);
    this.height = this._desiredHeight + dy;
    this.y = int(this.y - dy);

    this._lastMouseX = this.mouseX;
    this._lastMouseY = this.mouseY;
  }

  private function mouseMoveLeftBottomHandler(event:MouseEvent):void
  {
    var dx:Number = clampWidthDelta(this._lastMouseX - this.mouseX);
    this.width = this._desiredWidth + dx;
    this.x = int(this.x - dx);

    this.height = this._desiredHeight + clampHeightDelta(this.mouseY - this._lastMouseY);

    this._lastMouseX = this.mouseX;
    this._lastMouseY = this.mouseY;
  }

  private function mouseMoveRightTopHandler(event:MouseEvent):void
  {
    this.width = this._desiredWidth + clampWidthDelta(this.mouseX - this._lastMouseX);

    var dy:Number = clampHeightDelta(this._lastMouseY - this.mouseY);
    this.height = this._desiredHeight + dy;
    this.y = int(this.y - dy);

    this._lastMouseX = this.mouseX;
    this._lastMouseY = this.mouseY;
  }

  private function mouseMoveRightBottomHandler(event:MouseEvent):void
  {
    this.width = this._desiredWidth + clampWidthDelta(this.mouseX - this._lastMouseX);
    this.height = this._desiredHeight + clampHeightDelta(this.mouseY - this._lastMouseY);
    this._lastMouseX = this.mouseX;
    this._lastMouseY = this.mouseY;
  }

  private function mouseMoveLeftHandler(event:MouseEvent):void
  {
    var dx:Number = clampWidthDelta(this._lastMouseX - this.mouseX);
    this.width = this._desiredWidth + dx;
    this.x = int(this.x - dx);
    this._lastMouseX = this.mouseX;
    this._lastMouseY = this.mouseY;
  }

  private function mouseMoveRightHandler(event:MouseEvent):void
  {
    this.width = this._desiredWidth + clampWidthDelta(this.mouseX - this._lastMouseX);
    this._lastMouseX = this.mouseX;
    this._lastMouseY = this.mouseY;
  }

  private function mouseMoveTopHandler(event:MouseEvent):void
  {
    var dy:Number = clampHeightDelta(this._lastMouseY - this.mouseY);
    this.height = this._desiredHeight + dy;
    this.y = int(this.y - dy);
    this._lastMouseX = this.mouseX;
    this._lastMouseY = this.mouseY;
  }

  private function mouseMoveBottomHandler(event:MouseEvent):void
  {
    this.height = this._desiredHeight + clampHeightDelta(this.mouseY - this._lastMouseY);
    this._lastMouseX = this.mouseX;
    this._lastMouseY = this.mouseY;
  }
}
}

class DragType
{
  public static const Move:DragType = new DragType();
  public static const Top:DragType = new DragType();
  public static const Left:DragType = new DragType();
  public static const Right:DragType = new DragType();
  public static const LeftBottom:DragType = new DragType();
  public static const LeftTop:DragType = new DragType();
  public static const RightBottom:DragType = new DragType();
  public static const RightTop:DragType = new DragType();
  public static const Bottom:DragType = new DragType();
}